package lesson4.example5;

import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.FileUtils;

import com.alibaba.fastjson.JSON;
import com.classloading.utils.JavaCompilerUtil;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;

public class TestJavassist extends ClassLoader{

	public static String path = JavaCompilerUtil.path +"/bytecode-examples/bytecode-example-4-02/src/main/java/";
	public static String destpath = JavaCompilerUtil.path +"/bytecode-examples/bytecode-example-4-02/output";
	
	public static void main(String args[]) throws Exception {
		JavaCompilerUtil.compile(path+"/lesson4/example5/TestDemo.java", destpath);
		JavaCompilerUtil.compile(path+"/lesson4/example5/TestJavassist.java", destpath);
		JavaCompilerUtil.compile(path+"/lesson4/example5/MethodDesc.java", destpath);
		testJavassist();
		run();	
	}
	
	public static Map<Integer,MethodDesc>  map = new HashMap<Integer,MethodDesc>();
	
	public static void testJavassist() throws Exception {
		ClassPool pool = ClassPool.getDefault();
		pool.insertClassPath(destpath+"/lesson4/example5/TestDemo.class");

        // 获取类
        CtClass ctClass = pool.get("lesson4.example5.TestDemo");
        String className = ctClass.getName();

        // 获取方法
        CtMethod ctMethod = ctClass.getDeclaredMethod("foo");
        String methodName = ctMethod.getName();
        MethodInfo methodInfo = ctMethod.getMethodInfo();

        // 获取方法的入参信息
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        CtClass[] parameterTypes = ctMethod.getParameterTypes();

        int paramSize =  attr.tableLength(); 
        List<String> paramNameList = new ArrayList<>(paramSize);
        List<String> paramTypeList = new ArrayList<>(paramSize);    
        
        // 对于实例来说，params的值为  $1,$2
        StringBuilder params = new StringBuilder();  
        
		for (int i = 0; i < paramSize - 1; i++) {
			paramNameList.add(attr.variableName(i + 1));
			paramTypeList.add(parameterTypes[i].getName());
			params.append("$").append(i + 1);
			if (i != paramSize - 2)
				params.append(",");
		}
		
        // 方法的返回类型
        CtClass returnType = ctMethod.getReturnType();
        String returnTypeName = returnType.getName();
        
        // 记录增强方法的各种信息
        map.put(methodName.hashCode(), new MethodDesc(className, methodName, paramNameList, paramTypeList, returnTypeName));

        // 定义属性
        ctMethod.addLocalVariable("id", CtClass.intType);
        ctMethod.addLocalVariable("startNanos", CtClass.longType);
        ctMethod.addLocalVariable("parameterValues", pool.get(Object[].class.getName()));

        // 方法前增强
        ctMethod.insertBefore("{ startNanos = System.nanoTime(); parameterValues = new Object[]{" + params.toString() + "}; }");

        // 方法后增强
        // 在CtMethod的insertAfter()方法在目标方法的末尾插入一段代码。$_ 用来表示方法的返回值
        ctMethod.insertAfter("{ lesson4.example5.TestJavassist.point(" + methodName.hashCode() + ", startNanos, parameterValues, $_);}", false); 

        // 为方法添加TryCatch
        // $e表示异常值
	    ctMethod.addCatch("{ lesson4.example5.TestJavassist.point(" + methodName.hashCode() + ", $e); throw $e; }", ClassPool.getDefault().get("java.lang.Exception"));  
        
        ctClass.writeFile(destpath);
	}
	
	public static void point(final  int id, final long startNanos, Object[] parameterValues, Object returnValues) {
		MethodDesc method = map.get(id);
        System.out.println("===============（方法：" + method.getClassName() + "." + method.getMethodName()+")==============");
        System.out.println("入参类型：" + JSON.toJSONString(method.getParameterTypeList()) + "入参名称：" + JSON.toJSONString(method.getParameterNameList()) +" 入数值：" + JSON.toJSONString(parameterValues));
        System.out.println("出参类型：" + method.getReturnType() + " 出参值：" + JSON.toJSONString(returnValues));
        System.out.println("方法调用耗时：" + (System.nanoTime() - startNanos) / 1000000 + "(s)");
        System.out.println("==================================================================\r\n");
    }

    public static void point(final  int id, Throwable throwable) {
    	MethodDesc method = map.get(id);
    	System.out.println("==============（方法：" + method.getClassName() + "." + method.getMethodName()+")==============");
        System.out.println("异常：" + throwable.getMessage());
        System.out.println("==================================================================\r\n");
    }
    
    public static void run()  throws Exception{
		byte[] bytes = FileUtils.readFileToByteArray(new File(destpath + "/lesson4/example5/TestDemo.class"));
        Class<?> clazzNew = new TestJavassist().defineClass("lesson4.example5.TestDemo", bytes, 0, bytes.length);

        Method method = clazzNew.getMethod("foo", String.class, Integer.class);
        method.invoke(clazzNew.newInstance(), "3", 2);
        method.invoke(clazzNew.newInstance(), "a", 2);
	}

}
